/**
 * The OptionStepper component, similar to the NumericStepper, displays a single value, but is capable of displaying more than just numbers. It uses a DataProvider instance to query for the current value, therefore is able to support an arbitrary number of elements of different types. The dataProvider is assigned via code, as shown in the example  below:
<i>optionStepper.dataProvider = [“item1”, “item2”, “item3”, “item4”, “item5”];</i>

	<b>Inspectable Properties</b>
	A MovieClip that derives from the OptionStepper component will have the following inspectable properties:<ul>
	<li><i>visible</i>: Hides the component if set to false.</li>
	<li><i>disabled</i>: Disables the component if set to true.</li>
	<li><i>enableInitCallback</i>: If set to true, _global.CLIK_loadCallback() will be fired when a component is loaded and _global.CLIK_unloadCallback will be called when the component is unloaded. These methods receive the instance name, target path, and a reference the component as parameters.  _global.CLIK_loadCallback and _global.CLIK_unloadCallback should be overridden from the game engine using GFx FunctionObjects.</li>
	<li><i>soundMap</i>: Mapping between events and sound process. When an event is fired, the associated sound process will be fired via _global.gfxProcessSound, which should be overridden from the game engine using GFx FunctionObjects.</li></ul>

	<b>States</b>
	The OptionStepper component supports three states based on its focused and disabled properties. <ul>
	<li>default or enabled state.</li>
	<li>focused state, that highlights the textField area.</li>
	<li>disabled state.</li></ul>

	<b>Events</b>
	All event callbacks receive a single Object parameter that contains relevant information about the event. The following properties are common to all events. <ul>
	<li><i>type</i>: The event type.</li>
	<li><i>target</i>: The target that generated the event.</li></ul>

	The events generated by the OptionStepper component are listed below. The properties listed next to the event are provided in addition to the common properties.<ul>
	<li><i>show</i>: The component's visible property has been set to true at runtime.</li>
	<li><i>hide</i>: The component's visible property has been set to false at runtime.</li>
	<li><i>change</i>: The OptionStepper’s value has changed.</li>
	<li><i>stateChange</i>: The OptionStepper’s focused or disabled property has changed.<ul>
		<li><i>state</i>: Name of the new state. String type. Values "default", "focused" or "disabled".</li></ul></li></ul>
 */


import gfx.controls.Button;
import gfx.core.UIComponent;
import gfx.data.DataProvider;
import gfx.ui.InputDetails;
import gfx.ui.NavigationCode;
import gfx.utils.Constraints;


[InspectableList("disabled", "visible", "enableInitCallback", "soundMap")]
class gfx.controls.OptionStepper extends UIComponent
{
	/* PUBLIC VARIABLES */

	/** A reference to the currently selected item in the dataProvider. */
	public var selectedItem: Object;
	/** Mapping between events and sound process **/
	[Inspectable(type="Object", defaultValue="theme:default,focusIn:focusIn,focusOut:focusOut,change:change")]
	public var soundMap: Object = { theme:"default", focusIn:"focusIn", focusOut:"focusOut", change:"change" };


	/* PRIVATE VARIABLES */

	private var _dataProvider: Object;
	private var _selectedIndex: Number = 0;
	private var _labelField: String = "label";
	private var _labelFunction: Function;
	private var constraints: Constraints;


	/* STAGE ELEMENTS */

	/** A reference to the textField instance used to display the selected item's label. Note that when state changes are made, the textField instance may change, so changes made to it externally may be lost. */
	public var textField: TextField;
	/** A reference to the next button instance used to increment the {@code selectedIndex}. */
	public var nextBtn: Button;
	/** A reference to the previous button instance used to decrement the {@code selectedIndex}. */
	public var prevBtn: Button;


	/* INITIALIZATION */

	/**
	 * The constructor is called when a OptionStepper or a sub-class of OptionStepper is instantiated on stage or by using {@code attachMovie()} in ActionScript. This component can <b>not</b> be instantiated using {@code new} syntax. When creating new components that extend OptionStepper, ensure that a {@code super()} call is made first in the constructor.
	 */
	public function OptionStepper()
	{
		super();
		tabChildren = false;
		focusEnabled = tabEnabled = !_disabled;
		dataProvider = []; // Default Data.
	}


	/* PUBLIC FUNCTIONS */

	/**
	 * Disable this component. Focus (along with keyboard events) and mouse events will be suppressed if disabled.
	 */
	[Inspectable(defaultValue="false", verbose="1")]
	public function get disabled(): Boolean
	{
		return _disabled;
	}


	public function set disabled(value: Boolean): Void
	{
		if (_disabled == value) {
			return;
		}

		super.disabled = value;
		focusEnabled = tabEnabled = !_disabled;
		gotoAndPlay(_disabled ? "disabled" : (_focused ? "focused" : "default"));
		if (!initialized) {
			return;
		}

		updateAfterStateChange();
		prevBtn.disabled = nextBtn.disabled = _disabled;
	}


	/**
	 * The data model displayed in the component. The dataProvider can be an Array or any object exposing the appropriate API, defined in the {@code IDataProvider} interface. If an Array is set as the dataProvider, functionality will be mixed into it by the {@code DataProvider.initialize} method. When a new DataProvider is set, the {@code selectedIndex} property will be reset to 0.
	 * @see DataProvider#initialize
	 * @see IDataProvider
	 */
	public function get dataProvider(): Object
	{
		return _dataProvider;
	}


	public function set dataProvider(value: Object): Void
	{
		if (value == _dataProvider) {
			return;
		}

		if (_dataProvider != null) {
			_dataProvider.removeEventListener("change", this, "onDataChange");
		}

		_dataProvider = value;
		selectedItem = null;
		if (_dataProvider == null) {
			return;
		}

		// LM: I recommend that we move this check to the DataProvider.initialize(), and change it so it takes a second parameter (component instance).
		if ((value instanceof Array) && !value.isDataProvider) {
			DataProvider.initialize(_dataProvider);
		} else if (_dataProvider.initialize != null) {
			_dataProvider.initialize(this);
		}

		_dataProvider.addEventListener("change", this, "onDataChange");
		updateSelectedItem();
	}


	/**
	 * The index of the item in the {@code dataProvider} that is selected in a single-selection list.
	 */
	public function get selectedIndex(): Number
	{
		return _selectedIndex;
	}


	public function set selectedIndex(value: Number): Void
	{
		var newIndex:Number = Math.max(0, Math.min(_dataProvider.length-1, value));
		if (newIndex == _selectedIndex) {
			return;
		}

		_selectedIndex = newIndex;
		updateSelectedItem();
	}


	/**
	 * The name of the field in the {@code dataProvider} model to be displayed as the label in the TextInput field.  A {@code labelFunction} will be used over a {@code labelField} if it is defined.
	 * @see #itemToLabel
	 */
	public function get labelField(): String
	{
		return _labelField;
	}


	public function set labelField(value: String): Void
	{
		_labelField = value;
		updateLabel();
	}


	/**
	 * The function used to determine the label for an item. A {@code labelFunction} will override a {@code labelField} if it is defined.
	 * @see #itemToLabel
	 */
	public function get labelFunction(): Function
	{
		return _labelFunction;
	}


	public function set labelFunction(value: Function): Void
	{
		_labelFunction = value;
		updateLabel();
	}


	/**
	 * Convert an item to a label string using the {@code labelField} and {@code labelFunction}.
	 * @param item The item to convert to a label.
	 * @returns The converted label string.
	 * @see #labelField
	 * @see #labelFunction
	 */
	public function itemToLabel(item: Object): String
	{
		if (item == null) {
			return "";
		}

		if (_labelFunction != null) {
			return _labelFunction(item);
		} else if (_labelField != null && item[_labelField] != null) {
			return item[_labelField];
		}

		return item.toString();
	}


	/**
	 * The current data has become invalid, either by a data change, or a display change.
	 */
	public function invalidateData(): Void
	{
		_dataProvider.requestItemAt(_selectedIndex, this, "populateText");
	}


	public function handleInput(details: InputDetails, pathToFocus: Array): Boolean
	{
		var keyPress: Boolean = (details.value == "keyDown" || details.value == "keyHold");

		switch (details.navEquivalent) {
			case NavigationCode.RIGHT:
				if (_selectedIndex < _dataProvider.length-1) {
					if (keyPress) { onNext(); }
					return true;
				}
				break;
			case NavigationCode.LEFT:
				if (_selectedIndex > 0) {
					if (keyPress) { onPrev(); }
					return true;
				}
				break;

			case NavigationCode.HOME:
				if (!keyPress) { selectedIndex = 0; }
				return true;
			case NavigationCode.END:
				if (!keyPress) { selectedIndex = _dataProvider.length -1; }
				return true;
		}

		return false;
	}


	/** @exclude */
	public function toString(): String
	{
		return "[Scaleform OptionStepper " + _name + "]";
	}


	/* PRIVATE FUNCTIONS */

	private function configUI(): Void
	{
		super.configUI();
		nextBtn.addEventListener("click", this, "onNext");
		prevBtn.addEventListener("click", this, "onPrev");
		prevBtn.focusTarget = nextBtn.focusTarget = this;
		prevBtn.tabEnabled = nextBtn.tabEnabled = false;
		prevBtn.autoRepeat = nextBtn.autoRepeat = true;
		prevBtn.disabled = nextBtn.disabled = _disabled;

		constraints = new Constraints(this, true);
		constraints.addElement(textField, Constraints.ALL);
		updateAfterStateChange();
	}


	private function draw(): Void
	{
		if (sizeIsInvalid) {
			_width = __width;
			_height = __height;
		}

		super.draw();

		if (constraints != null) {
			constraints.update(__width, __height);
		}
	}


	private function changeFocus(): Void
	{
		gotoAndPlay(_disabled ? "disabled" : _focused ? "focused" : "default");
		updateAfterStateChange();
		prevBtn.displayFocus = nextBtn.displayFocus = (_focused != 0);
	}


	private function updateAfterStateChange(): Void
	{
		validateNow(); // Ensure that the width/height is up to date.
		updateLabel();
		if (constraints != null) {
			constraints.update(__width, __height);
		}

		dispatchEvent({type:"stateChange", state:_disabled ? "disabled" : _focused ? "focused" : "default"});
	}


	// Update the component label using the selectedItem.
	private function updateLabel(): Void
	{
		if (selectedItem == null) {
			return;
		}

		if (textField != null) {
			textField.text = itemToLabel(selectedItem);
		}
	}


	// Look up the label from the dataProvider
	private function updateSelectedItem(): Void
	{
		invalidateData();
	}


	private function populateText(item: Object): Void
	{
		selectedItem = item;
		updateLabel();
		dispatchEvent({type:"change"});
	}


	private function onNext(evtObj: Object): Void
	{
		selectedIndex += 1;
	}


	private function onPrev(evtObj: Object): Void
	{
		selectedIndex -= 1;
	}


	private function onDataChange(event: Object): Void
	{
		invalidateData();
	}
}
